#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include <sys/epoll.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include "wrap.h"
int main(int argc, char const *argv[])
{
        if(argc < 2){
                printf("eg: ./a.out port\n");
                exit(1);       
        }
        int port = atoi(argv[1]);

        int lfd = Socket(AF_INET, SOCK_STREAM,0);

        struct sockaddr_in serv_addr;
        socklen_t serv_len = sizeof(serv_addr);
        memset(&serv_addr,0,serv_len);
        serv_addr.sin_family = AF_INET;
        serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
        serv_addr.sin_port = htons(port);
        Bind(lfd,(struct sockaddr *)&serv_addr, serv_len);

        Listen(lfd,36);
        printf("Start accept ......\n");

        struct sockaddr_in client_addr;
        socklen_t client_len = sizeof(client_addr);

        //创建epoll树的根结点
        int epfd = epoll_create(3000);
        //初始化epoll树
        struct epoll_event ev;
        ev.events = EPOLLIN;
        ev.data.fd = lfd;
        epoll_ctl(epfd,EPOLL_CTL_ADD,lfd,&ev);

        struct epoll_event all[3000];
        while (1){
                //使用epoll通知内核检测文件IO
                int ret = epoll_wait(epfd,all,sizeof(all)/sizeof(all[0]),-1);
                //遍历all数组中的前ret个元素
                for (int i = 0; i < ret; i++){
                        int fd = all[i].data.fd;
                        //判断是否有新连接
                        if(fd == lfd){
                                //接收连接请求
                                int cfd = Accept(lfd,(struct sockaddr*)&client_addr,&client_len);
                                if(cfd == -1){perr_exit("accept error");}
                                printAddr("new client connection", &client_addr);
                                //设置cfd为非阻塞模式
                                int flag = fcntl(cfd, F_GETFL);
                                flag |= O_NONBLOCK;
                                fcntl(cfd,F_SETFL,flag);
                                //将新的cfd挂到树上
                                struct epoll_event temp;
                                //设置边沿触发
                                temp.events = EPOLLIN | EPOLLET;
                                temp.data.fd = cfd;
                                epoll_ctl(epfd,EPOLL_CTL_ADD,cfd,&temp);
                        }else{
                             //处理已连接的客户端发送过来的数据
                             if(!all[i].events & EPOLLIN){
                                     continue;
                             }
                             //读数据
                             char buf[5] = {0};
                             int len;
                             //循环的读取数据
                             while( (len = recv(fd,buf,sizeof(buf),0)) > 0){ //正常的数据收发
                                     //数据打印到终端
                                     Write(STDOUT_FILENO,buf,len);
                                     //发送给客户端
                                     send(fd,buf,len,0);
                             }
                             if(len == -1){
                                     if(errno == EAGAIN){ 
                                             //对于非阻塞的IO,最后一次强制读取也会返回-1 这个是正常现象 但是需要根据errno会被设置为EAGAIN来判断是否读完了
                                             //当errno被设置为EAGAIN不能直接退出程序
                                             printf("缓冲区数据已经读完\n");
                                     }else{
                                             perr_exit("recv error");
                                     }
                             }else if(len == 0){
                                     printf("客户端主动关闭了连接 ......\n");
                                     //fd从epoll树上删除
                                     ret = epoll_ctl(epfd,EPOLL_CTL_DEL,fd,NULL);
                                     if(ret == -1){
                                             perr_exit("epoll_ctl - del error");
                                     }
                                     Close(fd);
                             }
                        }
                }
        }
        Close(lfd);
        return 0;       
}